package hy.service.impl;

import com.alibaba.druid.util.StringUtils;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
import hy.beans.Product;
import hy.beans.Result;
import hy.dao.ProductDao;
import hy.exception.RetryException;
import hy.service.ProductService;
import org.springframework.aop.framework.AopContext;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.core.RedisOperations;
import org.springframework.data.redis.core.SessionCallback;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
@Service
@EnableAspectJAutoProxy(exposeProxy = true)//重要，把代理对象暴露出来
public class ProductServiceImpl implements ProductService {

    @Autowired
    private StringRedisTemplate tmp;
    @Autowired
    ProductDao dao;

    public List<Product> getAllProducts() {
        List<Product> list;
        //products是redis中一hashMap,其key为product_id值，value为product对象的json字符串
        if (tmp.hasKey("products")) {//从redis中加载数据
            list = new ArrayList<>();
            List<String> pro_str = (List<String>) (Object) tmp.opsForHash().values("products");
            for (String s : pro_str) {
                Product pr = JSON.parseObject(s, new TypeReference<Product>() {
                });
                list.add(pr);
            }
        } else {//从mysql中加载数据
            list = dao.selectAllProduct();
            for (Product p : list) {
                tmp.opsForHash().put("products", p.getId() + "", JSON.toJSONString(p));
            }
        }
        return list;
    }

    public Result addCart(String phone, String productId) {
        Result r = new Result();
        if (StringUtils.isEmpty(phone)) {
            return r;
        }
        if (tmp.opsForHash().size("cart_" + phone) > 0) {
            if (tmp.opsForHash().hasKey("cart_" + phone, productId)) {
                int number = Integer.parseInt((String) tmp.opsForHash().get("cart_" + phone, productId));
                tmp.opsForHash().put("cart_" + phone, productId, String.valueOf(number + 1));
            } else {
                tmp.opsForHash().put("cart_" + phone, productId, "1");
            }
        } else {
            tmp.opsForHash().put("cart_" + phone, productId, "1");
        }
        r.setSuccess(true);
        return r;
        //redis中hgetall cart_123
    }

    public String createOrder(String phone) {

        if (StringUtils.isEmpty(phone)) {
            return "你没有登录";
        }
        Map<String, String> map_cart = (Map<String, String>) (Object) tmp.opsForHash().entries("cart_" + phone);
        System.out.println("购物车:" + map_cart);
        if (map_cart.size() == 0) {
            return "购物车没商品";
        }
        String rs = setRedisProductNum(map_cart);
        //清空购物车
        if (rs.equals("success")) {
            tmp.delete("cart_" + phone);
        }
        return rs;
    }

    //减库存方法
    public String setRedisProductNum(Map<String, String> map_cart) {
        String rs = null;
        while (true) {
            rs = myLocl(map_cart);
            if ("success".equals(rs)) {
                break;
            }
        }
        return rs;//这里必须是success
    }

    //根据购物车中商品ID获取该商品在redis中的信息
    private List<Product> getProduct(Map<String, String> map_cart) {
        List<Product> list = new ArrayList<>();
        Set<String> product_ids_cart = map_cart.keySet();
        for (String id : product_ids_cart) {
            String t = (String) tmp.opsForHash().get("products", id);
            Product p = JSON.parseObject(t, new TypeReference<Product>() {
            });
            list.add(p);
        }
        return list;
    }

    //开启redis分布式乐观锁事务，库存不足回滚(redis中discard,mysql中rollback),如果被别人先修改了redis，则重新开redis事务重头再来
    @Transactional
    public String transac(Map<String, String> map_cart, List<Product> list) {
        //开事务
        tmp.multi();

        //根据购物车要购买的数量从两个数据库减库存
        for (Product p : list) {
            int sal_num = Integer.valueOf(map_cart.get(p.getId() + ""));
            if (p.getNum() < sal_num) {
                tmp.discard();//清空事务命令列表
                throw new RuntimeException("库存不足");//sql回滚
            }
            try {
                Thread.sleep(8000);
                System.out.println("开始减库存");
            } catch (InterruptedException e) {
            }

            p.setNum(p.getNum() - sal_num);
            //修改redis库存
            tmp.opsForHash().put("products", p.getId() + "", JSON.toJSONString(p));
            //修改mysql库存
            try {
                dao.updateProductNumById(p.getId(), p.getNum());
            } catch (Exception e) {
                tmp.discard();
                throw new RuntimeException("数据异常");//sql回滚
            }
        }
        if (tmp.exec().isEmpty()) {//事务提交失败，被别人先修改了
            throw new RetryException("被别人先下手了，重新来");//sql回滚
        }
        return "success";
    }

    private String myLocl(Map<String, String> map_cart) {
        //获取当前类的代理对象
        ProductService service = (ProductService) AopContext.currentProxy();
        tmp.watch("products");
        List<Product> list = getProduct(map_cart);
        String rs = null;
        try {
            //tmp默认执行一条命令后就会自动关闭连接，开事务就必须让一个连接连续执行多条命令,下面作法让同一个tmp中，完成多个操作
            rs = tmp.execute(new SessionCallback<String>() {
                public String execute(RedisOperations r) throws DataAccessException {
                    return service.transac(map_cart, list);
                    //return transac(map_cart, list);
                }
            });
        } catch (RetryException e) {
            //其他异常(sql异常，库存不足)继续抛出，不用重头再来，只有Retry异常重头再来并回滚sql
            System.out.println(e.getMessage());
        } finally {
            tmp.unwatch();
        }
        return rs;
    }
}